import sys, pprint, sets, code, StringIO, time, os

from SdlDotNet import *
from System.Drawing import Color, Point

from kant import KC
from page import Page

class MyInterpreter(code.InteractiveInterpreter):
    error = None
    def write(self, error):
        self.error = error

Characters = { "Space": " ",
               "Period": ".",
               "Comma": ",",
               "One": "1",
               "Two": "2",
               "Three": "3",
               "Four": "4",
               "Five": "5",
               "Six": "6",
               "Seven": "7",
               "Eight": "8",
               "Nine": "9",
               "Zero": "0",
               "Keypad1": "1",
               "Keypad2": "2",
               "Keypad3": "3",
               "Keypad4": "4",
               "Keypad5": "5",
               "Keypad6": "6",
               "Keypad7": "7",
               "Keypad8": "8",
               "Keypad9": "9",
               "Keypad0": "0",
               "KeypadMinus": "-",
               "KeypadPlus": "+",
               "KeypadMultiply": "*",
               "KeypadDivide": "/",
               "KeypadPeriod": ".",
               "Backquote": "`",
               "Minus": "-",
               "Equals": "=",
               "LeftBracket": "[",
               "RightBracket": "]",
               "Backslash": "\\",
               "Semicolon": ";",
               "Quote": "'",
               "Slash": "/",
               "Tab": "        ",
             }
ShiftCharacters = {
               "Period": ">",
               "Comma": "<",
               "One": "!", 
               "Two": "@", 
               "Three": "#", 
               "Four": "$", 
               "Five": "%", 
               "Six": "^", 
               "Seven": "&", 
               "Eight": "*",
               "Nine": "(", 
               "Zero": ")",
               "Backquote": "~", 
               "Minus": "_", 
               "Equals": "+",
               "LeftBracket": "{",
               "RightBracket": "}",
               "Backslash": "|",
               "Semicolon": ":",
               "Quote": '"',
               "Slash": "?",
             }


class InterpreterPage(Page):
    '''Run a Python interpreter.

    Backspace deletes text.

    The optional "text" may be used to "auto-type" into the interpreter.
    If activated by control-I any further kdjheystrokes will be taken from
    this text until the end of a line. Hitting return will then invoke
    the line as per usual and move the auto-typer on to the next line.
    Backspace is not handled. Shift-return will complete the current line
    and invoke it.

    The up and down keys will search through the input history.
    '''
    def __init__(self, title='', text='', sysver=False, **kw):
        #cursor = .Surface((10, 2))
        #pygame.draw.rect(cursor, KC.cursor_fgcolour, (0, 0, 10, 2))
        #self.cursor = cursor
        self.fontBd = KC.interp_font
        self.font = KC.interp_font
        if title:
            self.title = self.fontBd.Render(title, KC.titlecolour)
        else:
            self.title = None

        # figure the text to render and make it poppable
        self.auto_lines = [list(line)
            for line in text.strip().splitlines()]
        self.auto_lines.reverse()
        [line.reverse() for line in self.auto_lines]

        # actual rendered lines to draw to the screen
        self.rendered_lines = []

        # insert system version?
        if sysver:
            for line in sys.version.splitlines():
                self.rendered_lines.append(self.font.Render(line, 
                    KC.textcolour))

        # current typed line
        self.current_line = ['>', '>', '>', ' ']
        line = ''.join(self.current_line)
        self.rendered_lines.append(self.font.Render(line,
                    KC.textcolour))

        # input from the user which doesn't quite form complete
        # interpreter input
        self.captured_input = []

        self.history = []
        self.hist_num = 0

        self.auto_mode = False
        self.auto_line = []

        self.blink_time = 0
        self.blink_state = False
        self.shift = self.control = False
        Page.__init__(self, **kw)
    
    def keyRelease(self, sender, event):
        if event.Key in (Key.LeftShift, Key.RightShift):
            self.shift = False
        if event.Key in (Key.LeftControl, Key.RightControl):
            self.control = False

    def init(self):
        screen = Video.Screen
        self.interpreter = MyInterpreter({})
        sh = screen.Height
        if self.title:
            sh -= self.title.Height
        lh = self.font.Height
        self.max_rendered_lines = (sh - lh) / lh
        sw = screen.Width
        em = self.font.SizeText('M').Width
        self.columns = sw / em
        if sw % em: self.columns -= 1
        Page.init(self)

    def keyPress(self, sender, event):
        if True:
            if event.Key in (Key.LeftShift, Key.RightShift):
                self.shift = True
                return
            if event.Key in (Key.LeftControl, Key.RightControl):
                self.control = True
                return
            if event.Key == Key.Backspace:
                if not self.auto_mode and len(self.current_line) > 4:
                    self.current_line.pop()
                    self.render_current_line()
                return None
            if event.Key in (Key.Return, Key.KeypadEnter):
                if self.shift and self.auto_mode:
                    while self.auto_line:
                        glyph = self.auto_line.pop()
                        self.current_line.append(glyph)

                if self.auto_mode and self.auto_line:
                    # don't honor the return key yet
                    return None

                # evaluate the input
                input = ''.join(self.current_line[4:])

                # run the interpreter
                self.captured_input.append(input)
                input = '\n'.join(self.captured_input)
                #print input
                old_stdout = sys.stdout
                output = sys.stdout = StringIO.StringIO()
                more = self.interpreter.runsource(input)
                sys.stdout = old_stdout

                if not more:
                    self.captured_input = []

                # handle the output
                output.seek(0)
                out = []
                for line in output.readlines():
                    out.append(line.strip())
                #print "output", out, self.interpreter.error
                if self.interpreter.error:
                    out.append(self.interpreter.error.strip())
                    self.interpreter.error = None

                for line in out:
                    for n in range(len(line)/self.columns + 1):
                        text = line[n*self.columns:(n+1)*self.columns]
                        r = self.font.Render(text, KC.textcolour)
                        self.rendered_lines.append(r)

                # new line
                if input:
                    self.history.append(self.current_line)
                if more:
                    self.current_line = ['.', '.', '.', ' ']
                else:
                    self.current_line = ['>', '>', '>', ' ']

                # possibly remove an extraneous line?
                if len(self.rendered_lines) > self.max_rendered_lines:
                    diff = len(self.rendered_lines) - self.max_rendered_lines
                    self.rendered_lines = self.rendered_lines[diff:]

                # render the last line
                line = ''.join(self.current_line)
                r = self.font.Render(line, KC.textcolour)
                self.rendered_lines.append(r)

                self.hist_num = 0
                if self.auto_mode:
                    if not self.auto_lines:
                        self.auto_mode = False
                    else:
                        self.auto_line = self.auto_lines.pop()
                return None
            if False and not event.mod:
                # XXX tofix
                if event.Key == K_UP and self.history:
                    if not self.hist_num:
                        self.history.append(list(self.current_line))
                        self.hist_num = -1
                    if abs(self.hist_num) < len(self.history):
                        self.hist_num -= 1
                    self.current_line[:] = self.history[self.hist_num]
                    self.render_current_line()
                    return None
                if event.Key == K_DOWN and self.history:
                    if self.hist_num < -1:
                        self.hist_num += 1
                    self.current_line[:] = self.history[self.hist_num]
                    self.render_current_line()
                    return None
            if event.Key == Key.I and self.control:
                self.auto_mode = not self.auto_mode
                if self.auto_mode:
                    if self.auto_lines:
                        self.auto_line = self.auto_lines.pop()
                    else:
                        self.auto_mode = False
                self.current_line[:] = ['>', '>', '>', ' ']
                return None
            if False and event.Key == Key.D and event.mod & KMOD_CTRL:
                # XXX TOFIX
                # control-D quits interpreter
                return event

            glyph = str(event.Key)
            if self.shift and glyph in ShiftCharacters:
                glyph = ShiftCharacters[glyph]
            elif glyph in Characters:
                glyph = Characters[glyph]
            if len(glyph) > 1:
                print "unknown char", glyph
                return
            if not self.shift: glyph = glyph.lower()

            # is this a useful interpreter Key?
            if not glyph or ord(glyph) > 256:
                return event

            # use auto key or user key?
            if self.auto_mode:
                if self.auto_line:
                    glyph = self.auto_line.pop()
                else:
                    return None

            self.current_line.append(glyph)
            self.render_current_line()
            return None
        return event

    def render_current_line(self):
        line = ''.join(self.current_line)
        self.rendered_lines[-1] = self.font.Render(line, KC.textcolour)

    def render(self):
        screen = Video.Screen
        screen.Fill(self.bgcolour)

        #self.blink_time += deltat
        #if self.blink_time > 500:
            #self.blink_state = not self.blink_state
            #self.blink_time = 0

        toff = 0
        if self.title:
            screen.Blit(self.title, Point(0, 0))
            toff = self.title.Height

        for n, line in enumerate(self.rendered_lines):
            screen.Blit(line, Point(0, toff))
            toff += line.Height

        if False and not self.blink_state:
            x = line.Width
            pygame.draw.rect(screen, Config.cursor_fgcolour,
                (x, toff + n*(Config.interp_font_size + 2) + 32, 10, 2))
        Page.render(self)


